﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.ServiceModel;
using Microsoft.CodeMarkers.Contracts;

namespace Microsoft.CodeMarkers.Internal
{
    class CodeMarkerServiceHost : CodeMarkerBaseHost
    {
        object markerLock;
        ServiceHost Host { get; set; }
        Dictionary<string, ICodeMarkerClient> CurrentClients { get; set; }
        Action<int> RaiseInvoker { get; set; }

        public CodeMarkerServiceHost()
            : base(Process.GetCurrentProcess().Id)
        {
            this.markerLock = new object();
            this.CurrentClients = new Dictionary<string,ICodeMarkerClient>();
            this.RaiseInvoker = this.BeginRaise;
        }

        public void StartServer()
        {
            if (Host == null)
            {
                Host = new ServiceHost(typeof(CodeMarkerService), this.BaseUri);
                Host.AddServiceEndpoint(typeof(ICodeMarkerService), new NetNamedPipeBinding(), this.ServiceUri);
                Host.Open();
            }
        }

        public override void Dispose()
        {
            if (Host != null)
            {
                Host.Close();
                Host = null;
            }
        }

        public void RegisterClient(string key, ICodeMarkerClient client)
        {
            CurrentClients[key] = client;
        }

        internal void ReleaseClient(string key)
        {
            lock (markerLock)
            {
                if (this.CurrentClients.ContainsKey(key))
                {
                    this.CurrentClients.Remove(key);
                }
            }
        }

        public void Raise(int marker)
        {
            this.RaiseInvoker.BeginInvoke(marker, this.EndRaise, null);
        }

        private void BeginRaise(int marker)
        {
            if (CurrentClients != null)
            {
                List<string> clientsToRemove = new List<string>();

                lock (markerLock)
                {
                    foreach (KeyValuePair<string, ICodeMarkerClient> client in CurrentClients)
                    {
                        try
                        {
                            client.Value.OnMarkerRaised(marker);
                        }
                        catch (CommunicationException)
                        {
                            // Assume client has been disconnected improperly
                            clientsToRemove.Add(client.Key);
                        }
                    }
                }

                if (clientsToRemove.Count > 0)
                {
                    foreach (string key in clientsToRemove)
                    {
                        ReleaseClient(key);
                    }
                }
            }
        }

        private void EndRaise(IAsyncResult result)
        {
            this.RaiseInvoker.EndInvoke(result);
        }
    }
}
